package net.qiqbframework.commandhandling;


import net.qiqbframework.common.utils.StringUtils;
import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.messaging.annotation.ExecutableHandlerWrapper;
import net.qiqbframework.messaging.annotation.MessageExecutableHandler;
import net.qiqbframework.messaging.annotation.WrappedMessageExecutableHandler;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Optional;

public class CommandExecutableHandlerWrapper implements ExecutableHandlerWrapper {

    @Override
    public MessageExecutableHandler wrap(@Nonnull MessageExecutableHandler original) {
        final Optional<Object> commandName = original.attribute(CommandHandlerAttributes.COMMAND_NAME);
        if (commandName.isPresent()) {
            return new CommandMessageExecutableHandlerImpl(original);
        }
        return original;

    }

    private static class CommandMessageExecutableHandlerImpl
            extends WrappedMessageExecutableHandler
            implements CommandMessageExecutableHandler {

        private final String commandName;

        private final boolean isFactoryHandler;

        private CommandMessageExecutableHandlerImpl(MessageExecutableHandler delegate) {
            super(delegate);

            Executable executable = delegate.unwrap(Executable.class).orElseThrow(() -> new ConfigurationException("The @CommandHandler annotation must be put on an Executable (either directly or as Meta  Annotation)"));
            final String nameAttribute = delegate.attributes().get(CommandHandlerAttributes.COMMAND_NAME);
            commandName = StringUtils.isEmptyOrNull(nameAttribute) ? delegate.payloadType().getName() : nameAttribute;
            final boolean factoryMethod = executable instanceof Method && Modifier.isStatic(executable.getModifiers());
            if (factoryMethod && !executable.getDeclaringClass()
                    .isAssignableFrom(((Method) executable).getReturnType())) {
                throw new ConfigurationException("static @CommandHandler methods must declare a return value " +
                        "which is equal to or a subclass of the declaring type");
            }
            isFactoryHandler = executable instanceof Constructor || factoryMethod;
        }

        @Override
        public boolean canHandle(@Nonnull Message<?> message) {
            return super.canHandle(message) && commandName.equals(((CommandMessage<?>) message).getCommandName());
        }


        @Override
        public String commandNaming() {
            return commandName;
        }

        @Override
        public boolean isFactoryHandler() {
            return isFactoryHandler;
        }
    }
}
